#!/usr/bin/env python

'''
Provides motion detection functionality for the C{FrameGrabber}.
'''

import Image
import ImageFilter

class MotionDetector(object):
    '''
    Contains functionality to determine difference between two consecutive 
    frames and detect whether there is motion in the frames.

    >>> import Image
    >>> white_img = Image.new('L', (100, 100), 0xff)
    >>> black_img = Image.new('L', (100, 100), 0x00)
    >>> detector = MotionDetector()
    >>> detector.next_image(white_img)
    False
    >>> detector.next_image(black_img)
    True
    >>> detector.motion_detected
    True
    >>> detector.last_value
    1.0

    '''
    
    last_image = None
    last_value = 0.0
    motion_detected = False
    motion_image = None
    
    def __init__(self, threshold=0.5, mask_file=None, mask_image=None):
        '''
        Initializes the MotionDetector class with the following arguments.
        
        @param   threshold:      The level above which motion is detected
        @type    threshold:      Float
        @param   mask_image:     An image to overlay on each frame to block detection
                                 in areas of the mask that are black.  transparent
                                 pixels are the areas where motion will be detected.
        @type    mask_image:     PIL Image
        @param   mask_file:      Same as mask_image, but just the filename, which
                                 will be loaded into a PIL Image object if mask_image
                                 is None.
        @type    mask_file:      String
        '''
        self.threshold = threshold
        if mask_file:
            self.mask_image = Image.open(mask_file)
        else:
            self.mask_image = mask_image

    def next_image(self, image):
        '''
        Takes next available frame and performs detection routines.
        
        @param   image:  The next available video frame
        @type    image:  PIL Image
        @return:         True if motion was detected, otherwise False.
        @rtype:          Boolean
        '''
        
        image = image.convert('L')
        
        image = image.filter(ImageFilter.BLUR)
        image = image.filter(ImageFilter.MedianFilter(1))
        
        if not self.last_image:
            self.last_image = image
            self.motion_detected = False
            return self.motion_detected
        
        self.last_value = self.image_difference(
            self.last_image, image, self.mask_image)
        
        if self.last_value > self.threshold:
            self.motion_detected = True
        else:
            self.motion_detected = False
        
        self.last_image = image
        self.motion_image = image
        
        return self.motion_detected

    def image_difference(self, img1, img2, mask=None, thresh=30):
        '''
        Get the number of changed pixel of total pixels (changed/total).
        
        @param  img1:   The first image to compare
        @type   img1:   PIL image
        @param  img2:   The second image to compare
        @type   img2:   PIL image
        @param  mask:   Mask diff check in black areas in this image
        @type   mask:   PIL image
        @param  thresh: The min. amount of difference to identify
        @type   thresh: Number
        @return         Diff. pixels over total pixels (value between 0 and 1)
        @rtype          Float
        '''   

        d1 = list(img1.getdata())
        d2 = list(img2.getdata())
        total = len(d1)
        white = 0
        
        if not mask:
            dm = [255]*total
        else:
            dm = list(mask.getdata())
        
        for i in range(total):
            d = abs(d1[i] - d2[i])
            if d > thresh and dm[i]: white += 1
        try:
            return float(white) / float(total)
        except ZeroDivisionError: return 0.0

if __name__ == '__main__':
    
    import doctest
    doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE)
